<?php
/**
 * DependencyInjector class definition
 *  
 * @author Sean Crystal <seancrystal@gmail.com>
 * @package MVP-PHP
 */     
 
/**
 * A simple dependency injector class to implement the inversion of control pattern. 
 * @package MVP-PHP
 * @subpackage Base
 */   
class DependencyInjector 
{
	/**
     * namespace to use for singleton objects
	 */ 	
    const SINGLETON = 'SINGLETON';

	/**
     * Constructor
	 */ 	
	public function __construct()
	{
		$this->bindings   = array();
		$this->singletons = array();
	}
	
	/**
     * Binds an abstract interface name to a concrete class name within a namespace. The concrete
     * class must exist. 
	 * @param string $abstract
	 * @param string $concrete
	 * @param string $namespace
	 */   
    public function Bind($abstract, $concrete, $namespace)
    {
        Contract::Precondition(class_exists($concrete), '$concrete must be a valid class name');
		
		$namespace = ($namespace ? $namespace : 'DEFAULT');
				
		$this->bindings[$namespace][$abstract] = $concrete;
    }

	/**
     * Gets a new instance of the concrete class bound to an interface
	 * @param string $abstract
	 * @param string $namespace
	 * @return mixed 
	 */   	
    public function &GetInstance($abstract, $namespace = 'DEFAULT')
    {
		if (!is_array($this->bindings[$namespace]) || !(array_key_exists($abstract, $this->bindings[$namespace]))) {
			throw new UnboundDependencyException("Class $abstract not bound to concrete class in namespace $namespace");
		}

        $class = $this->bindings[$namespace][$abstract];

        return new $class();
    }

	/**
     * Gets a singleton instance of a concrete class bound to an interface. The injector will create the singleton
	 * instance if one does not already exist.
	 * @param string $abstract
	 * @return mixed 
	 */   	
    public function &GetSingleton($abstract)
    {
		if (!is_array($this->bindings[self::SINGLETON]) || !(array_key_exists($abstract, $this->bindings[self::SINGLETON]))) {
			throw new UnboundDependencyException("Class $abstract not bound to concrete class in namespace ". self::SINGLETON);
		}

		$class = $this->bindings[self::SINGLETON][$abstract];
		
		if (!($this->singletons[$abstract])) {
            $this->singletons[$abstract] = new $class();
        }

        return $this->singletons[$abstract];
    }
    
    /**
     * Checks whether an abstract interface is bound
	 * @param string $abstract
	 * @param string $namespace
	 * @return bool
	 */     
    public function Bound($abstract, $namespace = 'DEFAULT')
    {
		return (is_array($this->bindings[$namespace]) && array_key_exists($abstract, $this->bindings[$namespace]));
	}

	/**
     * contains the names of all the bound interfaces and classes
     * @var array 
	 */ 	
    private $bindings;
    
    /**
     * contains the singleton instances
	 * @var array
	 */   
    private $singletons;
}

?>